/*
 * Copyright (C) 2014 Square, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import { Charset } from "./utils/Charset"

/**
 * An [RFC 7235][rfc_7235] challenge.
 *
 * [rfc_7235]: https://tools.ietf.org/html/rfc7235
 */
export class Challenge{

  /** Returns the authentication scheme, like `Basic`. */
  _scheme: string
  get scheme(){
    return this._scheme
  }
  _authParams: Map<string | null, string>
  get authParams(){
    return this._authParams
  }

  constructor(scheme: string, authParams: Map<string | null, string>) {
    this._scheme = scheme
    // 转换键为小写
    this._authParams = new Map<string | null, string>()
    authParams.forEach((value, key) => {
      let newKey = key === null ? null : key.toLowerCase()
      this._authParams.set(newKey, value)
    })
  }

  /**
   * Convenience method to create a Challenge with just a realm.
   */
  static withRealm(scheme: string, realm: string): Challenge {
    let authParams = new Map<string | null, string>()
    authParams.set('realm', realm)
    return new Challenge(scheme, authParams)
  }

  /**
   * Returns the auth params, including [realm] and [charset] if present, but as
   * strings. The map's keys are lowercase and should be treated case-insensitively.
   */

  /** Returns the protection space. */
  get realm(): string | undefined {
    return this._authParams.get('realm')
  }

  /** The charset that should be used to encode the credentials. */
  get charset(): Charset {
    let charset = this._authParams.get('charset')
    if (charset !== null && charset !== undefined) {
      try {
        return Charset.forName(charset)
      } catch (ignore) {
      }
    }
    return Charset.forName('iso-8859-1')
  }

  /** Returns a copy of this Challenge that expects a credential encoded with [charset]. */
  withCharset(charset: Charset): Challenge {
    // 创建一个新的 Map 副本，不修改原始 authParams
    let newAuthParams = new Map<string | null, string>()
    this._authParams.forEach((value, key) => {
      newAuthParams.set(key, value)
    })
    newAuthParams.set('charset', charset.name())
    return new Challenge(this._scheme, newAuthParams)
  }

  equals(other?: unknown): boolean {
    if (other === null || other === undefined) return false
    if (!(other instanceof Challenge)) return false
    if (other.scheme !== this._scheme) return false
    // 比较 Map 内容
    return this.mapsEqual(this._authParams, other.authParams)
  }

  private mapsEqual(a: Map<string | null, string>, b: Map<string | null, string>): boolean {
    if (a.size !== b.size) return false
    for (const [key, value] of a) {
      if (!b.has(key) || b.get(key) !== value) return false
    }
    return true
  }

  hashCode(): number {
    let result = 29
    result = 31 * result + this.hashString(this._scheme)
    result = 31 * result + this.hashMap(this._authParams)
    return result
  }

  private hashString(str: string): number {
    let hash = 0
    for (let i = 0; i < str.length; i++) {
      const char = str.charCodeAt(i)
      hash = ((hash << 5) - hash) + char
      hash = hash & hash // Convert to 32bit integer
    }
    return hash
  }

  private hashMap(map: Map<string | null, string>): number {
    let hash = 0
    map.forEach((value, key) => {
      hash += this.hashString(key ?? '') ^ this.hashString(value)
    })
    return hash
  }

  toString(): string {
    return `${this._scheme} authParams=${JSON.stringify(Object.fromEntries(this._authParams))}`
  }
}
